package com.example.yupao.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.yupao.common.ErrorCode;
import com.example.yupao.exception.BusinessException;
import com.example.yupao.model.domain.Team;
import com.example.yupao.model.domain.User;
import com.example.yupao.model.domain.UserTeam;
import com.example.yupao.model.dto.TeamQuery;
import com.example.yupao.model.enums.TeamStatusEnum;
import com.example.yupao.model.request.TeamJoinRequest;
import com.example.yupao.model.request.TeamQuitRequest;
import com.example.yupao.model.request.TeamUpdateRequest;
import com.example.yupao.model.vo.TeamUserVO;
import com.example.yupao.model.vo.UserVO;
import com.example.yupao.service.UserService;
import com.example.yupao.service.UserTeamService;
import com.example.yupao.service.TeamService;
import com.example.yupao.mapper.TeamMapper;
import jodd.time.TimeUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
//improt org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* @author 宝儿姐
* @description 针对表【team(队伍)】的数据库操作Service实现
* @createDate 2024-06-19 15:51:21
*/
@Service
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
    implements TeamService{


    @Resource
    private UserTeamService userTeamService;

    @Resource
    private UserService userService;

    @Resource
    private RedissonClient redissonClient;


    /**
     * 创建队伍
     * @param team
     * @param loginUser
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class) // 加上事务, 保证创建队伍和把用户队伍关系插入到中间表是一个操作
    public long addTeam(Team team, User loginUser) {
        //1.请求参数是否为空
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //2.是否登录，未登录不允许创建
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        final long userId = loginUser.getId();
        //3.检验信息
        //(1).队伍人数>1且<=20
        int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);//如果为空，直接赋值为0
        if (maxNum < 1 || maxNum > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数不满足要求");
        }
        //(2).队伍标题 <=20
        String name = team.getName();
        if (StringUtils.isBlank(name) || name.length() > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题不满足要求");
        }
        // 3. 描述<= 512
        String description = team.getDescription();
        if (StringUtils.isNotBlank(description) && description.length() > 512) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述过长");
        }
        //4.status 是否公开，不传默认为0
        int status = Optional.ofNullable(team.getStatus()).orElse(0);
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
        if (statusEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态不满足要求");
        }

        //5.如果status是加密状态，一定要密码 且密码<=32
        String password = team.getPassword();
        if (TeamStatusEnum.SECRET.equals(statusEnum)) {
            if (StringUtils.isBlank(password) || password.length() > 32) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置不正确");
            }
        }
        //6.超出时间 > 当前时间
        Date expireTime = team.getExpireTime();
        if (new Date().after(expireTime)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "超出时间 > 当前时间");
        }

        //7.校验用户最多创建5个队伍
        //todo 有bug。可能同时创建100个队伍
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId);
        long hasTeamNum = this.count(queryWrapper);
        if (hasTeamNum >= 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建5个队伍");
        }
        //8.插入队伍消息到队伍表
        team.setId(null);
        team.setUserId(userId);
        boolean result = this.save(team);
        Long teamId = team.getId();
//        if (true) {
//            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建失败");
//        }
        if (!result || team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败");
        }
        //9. 插入用户 ==> 队伍关系 到关系表
        UserTeam userTeam = new UserTeam();
        userTeam.setTeamId(userId);
        userTeam.setTeamId(teamId);
        userTeam.setJoinTime(new Date());
        result = userTeamService.save(userTeam);
        if (!result) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败");
        }
        return teamId;
    }

    /**
     * 查询队伍信息, 附带有创建队伍人信息. (查询的就是 team 表, 不是关联表)
     * 没有条件, 查询的就是所有
     * @param teamQuery
     * @param isAdmin
     * @return
     */
    @Override
    public List<TeamUserVO> listTeams(TeamQuery teamQuery, boolean isAdmin) {
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        // 组合查询条件
        if(teamQuery != null) {
            // 校验 teamQuery 中的参数
            Long id = teamQuery.getId();
            if(id != null && id > 0) {
                // 查询相等条件
                queryWrapper.eq("id", id);
            }
            // 校验 teamQuery 中的参数idList
            List<Long> idList = teamQuery.getIdList();
            // CollectionUtils 注意是 apache.commons.collections4 底下的
            if(CollectionUtils.isNotEmpty(idList)) {
                // in 的用法就是 idList 里的id 都会被查到
                queryWrapper.in("id", idList);
            }

            String searchText = teamQuery.getSearchText();
            // 存在并且不为空字符串
            if (StringUtils.isNotBlank(searchText)) {
                queryWrapper.and((wrapper) -> {
                    // 两个模糊查询, 同时查名称或者描述模糊匹配
                    wrapper.like("name", searchText)
                            .or()
                            .like("description", searchText);
                });
            }
            // 队伍名称
            String name = teamQuery.getName();
            if(StringUtils.isNotBlank(name)) {
                queryWrapper.like("name", name);
            }
            // 队伍描述, 模糊查询
            String description = teamQuery.getDescription();
            if(StringUtils.isNotBlank(description)) {
                queryWrapper.like("description", description);
            }
            // 查询最大人数相等
            Integer maxNum = teamQuery.getMaxNum();
            if(maxNum != null && maxNum > 0) {
                queryWrapper.eq("maxNum", maxNum);
            }
            // 根据创建人来查询 (也就是队长)
            Long userId = teamQuery.getUserId();
            if(userId != null && userId > 0) {
                queryWrapper.eq("userId", userId);
            }

            // 根据登录人状态来查询, 只有管理员才能看加密和非公开的房间

            Integer status = teamQuery.getStatus();
            // 之前写过一个根据状态封装了一个枚举类
            TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
            if(statusEnum == null) {
                // 没有设置, 默认就是公开 (也就是前端不传递这个字段, 那就是公开的, 注意前端没有传递这个字段, 所以只能查询到公开的)
                statusEnum = TeamStatusEnum.PUBLIC;
            }
            // 此时不在判断 status, 而是判断statusEnum
            // 不是管理员, 也不是公开的, 那就报错, 没有权限

            // 此处修改为 不是管理员, 并且查看的是私人才会报权限错误. 也就是管理员可看所有, 个人可看公开和加密的
            // 因为我们也想要进行查询加密的队伍
//            if(!isAdmin && !statusEnum.equals(TeamStatusEnum.PUBLIC)) {
            if(!isAdmin && statusEnum.equals(TeamStatusEnum.PRIVATE)) {
                // 不是管理员, 也不是公开的, 那就报错, 没有权限
                throw new BusinessException(ErrorCode.NO_AUTH);
            }
            // 再取出 value 值. 感觉可以直接用 status 字段进行 0 1 2比较, 但是之前封装了一个枚举类,就还是用一下吧
            queryWrapper.eq("status", statusEnum.getValue());

            // 包装类要判断非空, 状态 0 1 2
//            if (status != null && status > -1) {
//                queryWrapper.eq("status", status);
//            }
        }

        // 不展示已过期的队伍, 这个是不需要判断的, 就是得带上的条件
        // 附加条件: 队伍没有设置过期时间, 也能查出来, 相当于是永久不过期
        // and expireTime is null or expireTime > now()
        // and 是连接上上面的条件
        queryWrapper.and(wrapper -> wrapper.isNull("expireTime").or().gt("expireTime", new Date()));

        // 查询出所有的队伍信息.  没有查询条件, 就是查询所有的队伍信息
        List<Team> teamList = this.list(queryWrapper);
        // 每次返回都要进行校验
        if(CollectionUtils.isEmpty(teamList)) {
            // 返回一个空数组
            return new ArrayList<>();
        }

        // 关联查询创建人的用户信息
        List<TeamUserVO> teamUserVOList = new ArrayList<>(); // 用于返回给前端的, 同时带有队伍信息和创建人信息
        for (Team team : teamList) {
            // 遍历队伍
            Long createId = team.getUserId();
            if(createId == null) {
                // 继续遍历下一个队伍
                continue;
            }
            TeamUserVO teamUserVO = new TeamUserVO(); // 用于返回给前端的封装对象类

            // 1. 先把队伍信息赋值给 teamUserVO: 返回给前端的封装对象类
            BeanUtils.copyProperties(team, teamUserVO);
            // 2. 查到创建人信息
            User createUser = userService.getById(createId);
            // 2.1 脱敏用户信息, 然后用户信息赋值到 teamUserVO
            if(createUser != null) {
                // 脱敏用户信息
                UserVO userVO = new UserVO(); // 专门返回给前端的用户信息封装类
                BeanUtils.copyProperties(createUser, userVO);
                // 用户信息赋值到 teamUserVO
                teamUserVO.setCreateUser(userVO);
            }
            // 添加到返回列表中
            teamUserVOList.add(teamUserVO);
        }
        return teamUserVOList;
    }


    /**
     * 更新队伍信息
     * @param teamUpdateRequest  前端可以传递的字段的封装类, 也就是team字段去掉一些不能修改的
     * @param loginUser
     * @return
     */
    @Override
    public boolean updateTeam(TeamUpdateRequest teamUpdateRequest, User loginUser) {
        if(teamUpdateRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = teamUpdateRequest.getId();
        Team oldTeam = getTeamById(id);

        // 只有队伍的创建者或者管理员才可以修改
        if(oldTeam.getUserId() != loginUser.getId() && !userService.isAdmin(loginUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH, "不是创建人或者管理员");
        }

        // ----------新加校验条件, 如果队伍状态改为加密, 必须得有密码
        Integer status = teamUpdateRequest.getStatus();
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
        // 就是用枚举类进行转换了一下
        // 判断枚举值是否设置为 加密了.
        if(statusEnum.equals(TeamStatusEnum.SECRET)) {
            // 是空的密码, 才进行报错
            if(StringUtils.isBlank(teamUpdateRequest.getPassword())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "加密房间必须要有密码");
            }
        }

        // 把 teamUpdateRequest 的属性赋值给team 对象
        Team team = new Team();
        BeanUtils.copyProperties(teamUpdateRequest, team);
        // 新加条件, 判断 名称和描述没有修改的话, 还是原来的样子
        if("".equals(team.getName())) {
            team.setName(oldTeam.getName());
        }
        if("".equals(team.getDescription())) {
            team.setDescription(oldTeam.getDescription());
        }
        boolean result = this.updateById(team);
        return result;
    }

    /**
     * 加入队伍  (已简单测试各种情况)
     * @param teamJoinRequest 前端传递的封装类(把一些不必要的字段剔除掉)
     * @param loginUser
     * @return
     */
    // todo 疯狂点击, 有可能重复加入, 所以要加入锁.
    @Override
    public boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUser) {

        // 队伍 id 不存在
        Long teamId = teamJoinRequest.getTeamId();
        Team team = getTeamById(teamId);
        // 判断是否过期
        Date expireTime = team.getExpireTime();
        // date 用 before 方法判断是否在之前
        if(expireTime != null && expireTime.before(new Date())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍信息已过期");
        }
        // 不能加入私有队伍
        Integer status = team.getStatus();
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
        if(TeamStatusEnum.PRIVATE.equals(statusEnum)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍是私有的, 不能加入");
        }

        // 加密房间, 前端传递的密码错误
        String password = teamJoinRequest.getPassword();
        if(TeamStatusEnum.SECRET.equals(statusEnum)) {
            if(StringUtils.isBlank(password) || !team.getPassword().equals(password)) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误或者为空");
            }
        }
        // 这里如果疯狂点击加入, 就会重复插入, 所以要加锁, synchronized 是单机锁, 所以还是要加分布式锁z


        // 该用户已加入的队伍数量 数据库查询所以放到下面，减少查询时间
        // 判断用户最多加入 5 个队伍 , 去关系表中查询
        long userId = loginUser.getId();
        //---------------新添加: 可以看视频
        RLock lock = redissonClient.getLock("yupao:join_team");


        try {
            // 抢到锁并执行, 此处为了保证前来加入队伍的都能加入, 所以采用了一个循环
            while (true) {
                if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                    System.out.println("getLockL: " + Thread.currentThread().getId());

                    QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
                    userTeamQueryWrapper.eq("userId", userId);
                    // 用count 查条数
                    long hasJoinTeam = userTeamService.count(userTeamQueryWrapper);
                    if (hasJoinTeam >= 5) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "加入失败, 最多加入 5 个队伍, 现已超出");
                    }

                    // 不能重复加入已加入的队伍
                    // 判断条件就是去中间表中, 查询 userId 和 teamId 对应上的存不存在

                    // 注意: 需要创建一个新的 QueryWrapper, 不然查询就会带上之前加的条件
                    QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("userId", userId);
                    queryWrapper.eq("teamId", teamId);
                    long hasUserJoinTeam = userTeamService.count(queryWrapper);
                    if (hasUserJoinTeam > 0) {
                        // 说明已经加入了这个队伍
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "加入失败, 用户已加入这个队伍");
                    }

                    // 判断已加入队伍的人数是不是超过最大人数
                    queryWrapper = new QueryWrapper<>();

                    queryWrapper.eq("teamId", teamId);
                    long joinNum = userTeamService.count(queryWrapper);
                    if (joinNum > team.getMaxNum()) {
                        // 说明已经加入了这个队伍
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "加入失败, 队伍已满");
                    }

                    // 到这里, 可以把用户添加到队伍当中. 也就是添加到中间表中
                    UserTeam userTeam = new UserTeam();

                    userTeam.setUserId(userId);
                    userTeam.setTeamId(teamId);
                    userTeam.setJoinTime(new Date()); // 加入时间就是插入时间
                    // 保存到中间表中
                    boolean save = userTeamService.save(userTeam);
                    return save;
                }
            }
        }catch(InterruptedException e){
            log.error("redisson getLock error", e);

            // 得有返回值, 这里得加上
            return false; // 不报错, 一定返回return, 报错了就返回false, 表示没执行成功
        }finally {
            // 释放自己的锁(和缓存预热一样)
            if(lock.isHeldByCurrentThread()) {
                System.out.println("unlock: " + Thread.currentThread().getId());
                lock.unlock();
            }
        }

//        QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
//        userTeamQueryWrapper.eq("userId", userId);
//        // 用count 查条数
//        long hasJoinTeam = userTeamService.count(userTeamQueryWrapper);
//        if(hasJoinTeam >= 5) {
//            throw new BusinessException(ErrorCode.PARAMS_ERROR, "加入失败, 最多加入 5 个队伍, 现已超出");
//        }
//
//        // 不能重复加入已加入的队伍
//        // 判断条件就是去中间表中, 查询 userId 和 teamId 对应上的存不存在
//
//        // 注意: 需要创建一个新的 QueryWrapper, 不然查询就会带上之前加的条件
//        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("userId", userId);
//        queryWrapper.eq("teamId", teamId);
//        long hasUserJoinTeam = userTeamService.count(queryWrapper);
//        if(hasUserJoinTeam > 0) {
//            // 说明已经加入了这个队伍
//            throw new BusinessException(ErrorCode.PARAMS_ERROR, "加入失败, 用户已加入这个队伍");
//        }
//
//        // 判断已加入队伍的人数是不是超过最大人数
//        queryWrapper = new QueryWrapper<>();
//
//        queryWrapper.eq("teamId", teamId);
//        long joinNum = userTeamService.count(queryWrapper);
//        if(joinNum > team.getMaxNum()) {
//            // 说明已经加入了这个队伍
//            throw new BusinessException(ErrorCode.PARAMS_ERROR, "加入失败, 队伍已满");
//        }
//
//        // 到这里, 可以把用户添加到队伍当中. 也就是添加到中间表中
//        UserTeam userTeam = new UserTeam();
//
//        userTeam.setUserId(userId);
//        userTeam.setTeamId(teamId);
//        userTeam.setJoinTime(new Date()); // 加入时间就是插入时间
//        // 保存到中间表中
//        boolean save = userTeamService.save(userTeam);
//        return save;
    }

    private Team getTeamById(Long teamId) {
        if(teamId == null || teamId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍不存在");
        }
        // 再多判断一下 team 对象吧
        Team team = this.getById(teamId);
        if(team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍不存在");
        }
        return team;
    }


    /**
     * 退出队伍
     * @param teamQuitRequest  前端传递的封装类(把一些不必要的字段剔除掉)
     * @param loginUser
     * @return
     */
    @Override
    public boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser) {

        long userId = loginUser.getId();
        // 判断队伍是够存在
        Long teamId = teamQuitRequest.getTeamId();
        Team team = getTeamById(teamId);

        // 判断自己是不是加入了队伍, 没加入队伍肯定不能退出队伍
        UserTeam userTeam = new UserTeam();
        userTeam.setUserId(userId);
        userTeam.setTeamId(teamId);
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>(userTeam);
        // 然后查询
        long count = userTeamService.count(queryWrapper);
        if(count == 0) {
            // 说明没有加入
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户未加入队伍");
        }
//        if(count > 0) {
//            // 说明重复了, 我们删除重复的, 可以进行补充.
//            while(count == 1) {
//                userTeamService.remove(queryWrapper); // 移除掉多余的, 只剩下一条
//                count--;
//                // count = userTeamService.count(queryWrapper);
//            }
//            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户重复加入队伍");
//        }
        // 查询这个队伍中有多少人
        long teamHasJoinNum = countTeamUserById(teamId);
        if(teamHasJoinNum == 1) {
            // 只有一个人, 直接解散队伍, 也就是自己
            this.removeById(teamId);
        } else {
            // 队伍至少还有两名
            // 是队长.
            if(team.getUserId() == userId) {
                // 把队长交给最早加入的用户, 根据 id 升序筛选. 最早加入也就是最早插入表中信息, id最小
                // 此处我们只查询两个, 一个是队长, 一个是第二早加入的队长
                QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
                userTeamQueryWrapper.eq("teamId", teamId);
                // 拼接上sql
                userTeamQueryWrapper.last("order by id asc limit 2");
                // 查询所有满足条件的数据
                List<UserTeam> userTeamList = userTeamService.list(userTeamQueryWrapper);
                if(CollectionUtils.isEmpty(userTeamList) || userTeamList.size() <= 1) {
                    // 说明没有查到两个人就报错
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "队伍人数不足");
                }
                // 拿到第二早加入的用户
                UserTeam nextUserTeam = userTeamList.get(1);
                Long nextTeamLeadId = nextUserTeam.getUserId();
                // 更新当前的队长给第二个人. 此处更新的是 team 表
                Team updateTeam = new Team();
                updateTeam.setUserId(nextTeamLeadId);
                // 此处我们也设置一下主键id, 还是之前的主键 id
                updateTeam.setId(teamId);
                boolean result = this.updateById(updateTeam);
                System.out.println("quit: 已设置成功");
                if(!result) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新队伍队长失败, 内部sql 错误");
                }
            }
        }
        // 注意解散关联表信息. 不论有几个人, 是不是队长, 都得把中间表关系删除掉
        boolean remove = userTeamService.remove(queryWrapper);
        return remove;
    }

    /**
     * 队长解散队伍
     * @param id
     * @param loginUser
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    // 注意在操作多个数据库时，在方法上要加上@Transactional(rollbackFor  = Exception.class)注解，
    // 表示要么数据操作都成功，要么都失败。
    public boolean deleteTeam(long id, User loginUser) {
        Team team = getTeamById(id);
        Long teamId = team.getId();
//        if(!team.getUserId().equals(loginUser.getId())) {
        if (team.getUserId() != loginUser.getId()) {
            // 不是队长, 没有权限
            throw new BusinessException(ErrorCode.NO_AUTH, "不是队长, 不可以删除");
        }
        // 移除所有加入队伍的关联信息
        QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
        userTeamQueryWrapper.eq("teamId", teamId);
        boolean result = userTeamService.remove(userTeamQueryWrapper);
        if (!result){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR,"删除队伍关联信息失败");
        }
        // 删除队伍
        return this.removeById(teamId);
    }

    private long countTeamUserById(Long teamId) {
        QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
        userTeamQueryWrapper.eq("teamId", teamId);
        long teamHasJoinNum = userTeamService.count(userTeamQueryWrapper);
        return teamHasJoinNum;
    }
}




